コンセプトから学ぶAmazon DynamoDB【Amazon RDSとの比較篇】
よく訓練されたアップル信者、都元です。AWS上のシステム設計において、どんな時にRDSを選択するのか、そしてどんな時にDynamoDBを選択するのか。比較しながら見て行きたいと思います。
RDBとNoSQL
ACIDなRDB
一昔前、一般的に「データベース」と言えば、多くはリレーショナルデータベース(RDB)のことを指していました。テーブルと呼ばれる「行とカラムで構成される二次元のデータ構造」に対して、SQLと呼ばれる強力なクエリ言語で操作を行い、データの一貫性(Consistency: どこから観測しても同じ値が得られること)や操作の原子性(Atomicity: 一連の操作を全て適用commitするか、全てキャンセルrollbackするかの二択として実現できること)を実現するモデルは、開発者を含むシステムの利用者にとって非常に理解しやすく、広く受け入れられて来ました。多くの方がご存知の通りです。
このようなデータベースが持つ強い整合性を伴う特徴を、ACID(Atomicity・Consistency・Isolation・Durability)特性と呼ぶことがあります。
また、RDBはSQLによって、あらゆるカラムをWHERE句の条件に指定でき、自由自在にデータを絞り込んで検索することが可能です。インデックスの有無によるパフォーマンスの違いはありますが、検索条件の自由度が非常に高いのも大きな特徴です。
さてその一方で、システムに対する要求(本稿の話題においては特に非機能要求)は日に日に高まり、高い可用性(Availability: データが常に読み書きできること)と拡張性(Scalability: 大量のデータ書き込み/読み出し要求に応えるための規模拡張が容易にできること)が求められるシステムが多くなって来ています。
要するに、基本的にシステムのダウンは許さん。そして大量のデータアクセスに耐えられるようにせよ。という要求です。
一般的に、高い可用性を得るには「冗長化」作戦が有効です。データのコピーを複数のノードに持ち、仮にノードの1つが障害を起こしても他のノードが応答できる状態を作るのです。RAID1と言えば分かる人には分かりやすいでしょうか。
また高い拡張性は「シャーディング」作戦が有効です。これもまた複数のノードを用意し、データごとに受け持つノードを分けることで、性能を担保します。こちらはRAID0と言えば分かりやすいかもしれません。
ただ、このようにデータを分散して保持すると、一貫性の維持が困難になります。データの書き換え時に、全ノードに変更が行き渡ることを保証するのが大変なのです。結果として、一貫性と可用性の両立には大きな困難が伴います。
一貫性のあるデータベースは、前述の通り理解しやすく説明もしやすい(例えばお客様に対して)ため、高可用性・高拡張性を要件としないシステムにおいては第一選択となります。
BASEなNoSQL
一方、冷静に考えると、実は「一貫性」というのは(そりゃ、あるに越したことは無いんでしょうが)実は必須でない場合があります。確かに説明がしやすいですが、例えばデータの編集操作をしたらその瞬間以降は、確実に新しい値が見えるべきで、一瞬たりとも古い値が見えてはならない、という要件は必ずしも必須でない場合があります。もちろん無いとは言いませんが。
例えば「書き換えたはずなのに古い値が見えるなんて、なんか気持ち悪い。お客様にどう説明しよう?」と悩むのは嫌なので一貫性が欲しい。また、ダウン時の機会損失が大きなシステムであるため、可用性の低下は許容できない。この場合、簡単に(低コストで)これらを両立させることは難しいので、一貫性に関する考えを精査していくことが求められます。精査の結果「システム要件として絶対に一貫性が必要である」という結論となると詰むかもしれませんが、前述の通り「なんか気持ち悪い」というだけであれば、その気持ち悪さと機会損失どっちを取るんだ、という選択を迫ることになります。
つまり、高可用性・高拡張性を要件とするシステムにおいては、一貫性を一部犠牲にすることで大きな価値を得られるがあるケースがあります。このようなケースにハマるデータベースが、いわゆるNoSQLです。
NoSQLはACIDではなくBASE(Basically Available・Soft state・Eventually consistent)という特性をで語られます。細かい説明は他の資料に譲りますが、BASE *1は高い可用性を持ちます。そして、一貫性が無いとは言え、それは即座に一貫性が保証されないだけで、結果的(Eventually)には一貫性を持つ状態に収束する、所謂「結果整合性」という特徴を持っています。
ところで、前述の通り、RDBの検索条件の自由度は高い一方、NoSQLでは(一般的に)事前にキーとして指定した要素を使うことでしか目的のデータを探せません。キーとして指定した要素以外を使って値を探し出したい場合は、全体をスキャン(走査)して条件に一致するものを1つ1つ選別していく必要があり、パフォーマンス面で非常に不利(大抵の場合、非現実的)となります。
というわけで、高い可用性または拡張性を要求するシステムにおいては、一定の条件を飲むことができれば、NoSQLが第一選択となり得ます。
RDSとDynamoDB
さて、AWSにおいてRDBの代表プロダクトは「RDS」、NoSQLの代表プロダクトは「DynamoDB」であります。
ACIDなRDS
前述のとおり、RDSは高い一貫性を持ちます。では、可用性や拡張性は無いのでしょうか?
いや、無いと言ってしまうとあんまりなんですが、まぁ弱いんです。
RDSは、Multi-AZの構成にしたとしても、稀に何らかの障害等により、MasterからSlaveへのフェイルオーバーが発生することがあります。そしてこのフェイルオーバーの処理を行っている数分間は、可用性が損なわれます。また、障害ではなかったとしても、例えばセキュリティパッチの適用等の目的で、メンテナンスウィンドウにおける再起動が起こることがあります。つまり、RDSにおいてはサービスレベルの設計時点でメンテナンスウィンドウにおいて可用性が損なわれることは織り込み済みの事象と言えます。
また、拡張性については、RDSはスケールアップのみをサポートします。つまり、インスタンスタイプの変更です。規模の拡大に伴ってスケールアップを繰り返すと、すぐに限界が訪れてしまう可能性があります。読み込みだけに絞れば、Read replicaによるスケールアウトに対応しますが、レプリカをぶら下げられる数にも一定の制限があり、比較的天井が低いのが実情です。
逆に言えば、この「織り込み済みの可用性低下」や「拡張性の天井」を許容できないシステムにおいては、RDSという選択をしてはいけません。
BASEなDynamoDB
という状況から生まれたのがDynamoDBです。DynamoDBは高い可用性を持ちます。言い換えると、織り込み済みの可用性低下は無く、基本的にいつでも利用可能(Basically Available)であることを想定してサービス設計がされています。
また、テーブル設計(キー設計)に一定の考慮が必要ではあるのですが、水平スケーリング(シャーディングによるキャパシティの確保)の機能を持ち、想定した負荷に対応できるような体制を構えることが可能です。
一方、一貫性はEventualであるため、データを更新した直後には一定の確率で古いデータが取得できてしまうことがあります。しかしその状態は長くは続かず(通常1秒以内と言われています)、結果的には常に新しいデータが取得できるようになります。ただし、DynamoDBには「強い整合性の読み込み」というモードがあり、この操作を行うと、費用と引き換えに、必ず新しい値を返すことを要求できます。便利!
そして、DynamoDBにはトランザクションがありませんので、2つの操作をall or nothingで行う原子性を持たせる機能はありません。が、この辺りはどうしても欲しければクライアント側で頑張って実装したりすると実現可能なようです。難しそうですが。
また、先ほども触れた通り、DynamoDBでは自由な絞り込み条件で値を探すことができません。事前に設定したキーに対応する値を取ってくるのが基本的な操作です。DynamoDBでは、1つのテーブルにつき1つの主キーを設定できますが、それに加え、5つのセカンダリインデックスと呼ばれるキーを設定できます。つまり、事前に決定した1+5通りの要素で検索ができます。しかし、それ以外の要素による検索は、全走査による選別作業になってしまいます。
この点は、後からいかようにでもWHERE句で検索条件を指定できるRDBと違い、テーブル設計の重要性がRDB以上に大きいと言えます。
まとめ
RDS | DynamoDB | |
---|---|---|
一貫性 | 強力 | 結果整合なので基本的に弱い(強整合性指定も可能) |
原子性 | ある | ない(同じアイテム内の更新であれば可能) |
検索条件 | SQLのWHERE句で自由自在 | 事前指定のキーまたはインデックスのみ |
可用性 | メンテナンスウィンドウあり | 基本的に常に利用可 |
拡張性 | スケールアップのみで天井が低い | シャーディングによるスケールアウトが可能 |
いかがでしたでしょうか。AWS上で稼働するシステムのデータストアとして、RDSを選ぶ場面とDynamoDBを選ぶ場面、それぞれぼんやりとでも想像できましたでしょうか。
例えばマイクロサービス的なコンポーネントを作っていて、複数のノード間でシンプルなデータ共有をする必要がある場合を考えてみます。楽なのはRDSを立ててしまうことですが、メンテナンスウィンドウ等による可用性低下を受け入れる必要があります。
仮に大した性能要件も必要なかったとしても、シンプルなデータ共有なのであれば、可用性を求めてDynamoDBを使う、という選択肢が出てきます。性能要件が小さければ、RDSインスタンスを維持する場合と比較して、費用面でも有利になる可能性があります。
システム設計時の選択肢として、是非DynamoDBもあなたの道具箱に入れておいてください。
最後に余談ですが、このRDSとDynamoDBのサービスアイコン。RDSはモノリシックなコンポーネントとしてドンと鎮座しており、DynamoDBはシャーディングによる水平スケーリングを実現している。そんな想いが込められているのでしょう。
脚注
- 余談ですが、ACID(酸)に対してBASE(塩基=アルカリ)とは、なかなか上手にシャレを当ててきたなぁ、と思います、ハイ。 ↩